Assuming a json string like the following:
string json = '{"string_property":"foo_bar", ... other objects here ...}';
I was wondering if there's a way to run a transformation on the parsed object so that instead of getting foo_bar, I'll get foo bar after running the following method (can be anything really)
public string Transform(string s) {
return s.Replace("_"," ");
}
I can manually alter my poco after deserializing, but wondered what would be a "cleaner" approach?
You can transform your string properties as you deserialize your root object by using a custom JsonConverter targeted at all string type values:
public class ReplacingStringConverter : JsonConverter
{
readonly string oldValue;
readonly string newValue;
public ReplacingStringConverter(string oldValue, string newValue)
{
if (string.IsNullOrEmpty(oldValue))
throw new ArgumentException("string.IsNullOrEmpty(oldValue)");
if (newValue == null)
throw new ArgumentNullException("newValue");
this.oldValue = oldValue;
this.newValue = newValue;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = (string)JToken.Load(reader);
return s.Replace(oldValue, newValue);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then use it like:
var settings = new JsonSerializerSettings { Converters = new[] { new ReplacingStringConverter("_", "") } };
var result = JsonConvert.DeserializeObject<RootObject>(json, settings);
Note however that if individual string-type properties have their own converters applied directly with [JsonConverter(Type)], those converters will be used in preference to the ReplacingStringConverter in the Converters list.
I've ended up doing the following:
First, create a converter that only reads and all it does is url decode the string.
public class UrlDecoderConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = (string)JToken.Load(reader);
return HttpUtility.UrlDecode(s);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, simply add the following to the POCO properties that need to be decoded:
[JsonConverter(typeof(UrlDecoderConverter))]
public string url { get; set; }
Related
I have a requirement for some deserialization I'm trying to handle where I could have these potential inputs:
{
"value": "a string"
}
-- or --
{
"value": {
"text": "a string"
// there are other properties, but for successful deserialization I only need text present
}
}
And I expect it to be able to successfully convert to the object, MyObject:
public class MyObject
{
[JsonProperty("text")
public string Text { get; set; }
}
So far this is what I have in my converter. This case works fine when it's a string (although not very efficient because I'm throwing an exception to catch a failed deserialization). However, when it's an object the reader throws an exception and I'm uncertain of how to handle it.
public class MyObjectConverter : JsonConverter
{
public override bool CanWrite { get => false; }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string) || objectType == typeof(MyObject);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = reader.Value?.ToString();
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
try
{
return JObject.Parse(value).ToObject<MyObject>();
}
catch (Exception)
{
return new MyObject
{
Text = value
};
}
}
}
perhaps I'm there is already a nice way to do this that I'm unaware of? If not, how can I determine whether my input is a string or an object to be able to return the object I care about?
SOLUTION:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
return new MyObject
{
Text = reader.Value?.ToString()
};
}
else if (reader.TokenType == JsonToken.StartObject)
{
JObject obj = JObject.Load(reader);
return obj.ToObject<MyObject>();
}
else
{
return null;
}
}
Your Object isn't structured like the input your expecting. For the second case, MyObject would need to look like this:
// this is ugly
public class MyObject
{
public Value value{get;set;}
public class Value{
public string text {get;set;}
}
}
If you want to have just an object with a single Text property like you currently do, you could do something like this:
public override object ReadJson(JsonReader reader, Type objectType, object
existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
var value = obj["value"];
if(value is JObject) // this will be true if the value property is a nested structure
return new MyObject(){Text=value["text"]}; // could also do value.ToObject<MyObject>() if you need more properties
else
return new MyObject(){Text=value};
}
EDIT: Clarify question:
I have overridden the JsonConverter for a base type (by applying [JsonConverter(typeof(TConverter))] to the superclass), but when deserializing the sub-type directly I want to use STANDARD serialization (i.e. no custom converter) for deserializing my derived object. How do I specify STANDARD serialization for use in the deserialize method, as if I had NOT overridden the JsonConverter?
I am using elastic search and can't call JsonConvert.DeserializeObject with my custom implementation of JsonConverter, and have to rely on the attribute for Elastic to use my converter.
However, using this converter as attribute seems to affect all sub classes as well, but I just want them to use the standard converter, so that I don't have to implement JsonConverter for each of many implementations.
This is my classes/logic as I would like it to look:
[Route("test")]
[HttpPost]
public HttpResponseMessage Test([FromBody] JToken json)
{
var res = json.ToObject<Product>(); // I want an object of ProductImpl type here
return Request.CreateResponse(res);
}
[JsonConverter(typeof(JsonProductConverted))]
public abstract class Product
{
}
public class ProductImpl : Product
{
}
public class JsonProductConverted : 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)
{
JObject json = JObject.Load(reader);
//var type = GetTypeFromId((int) json["typeId"]); // Construct type from field in
var type = typeof(ProductImpl);
// var res = JsonConvert.DeserializeObject(json.ToString(), type, DEFAULT_JSONCONVERTER_HERE);
var res = DeserializeToObjectWithStandardJsonConverter(json, type);
return res;
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
If I don't supply the default JsonConverter, or similar it will just use the JsonProductConverted converter, which creates an infinite loop.
Since you have added [JsonConverter(typeof(JsonProductConverted))] directly to your Product type, you could add a dummy converter to ProductImpl that returns false from CanRead and CanWrite:
[JsonConverter(typeof(NoConverter))]
public class ProductImpl : Product
{
}
public class NoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return false;
}
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This overrides the base class's converter and then falls back on default serialization for both reading and writing
Sample .Net fiddle.
Another option would be to use serializer.Populate(). This avoids the call to the converter for the object itself:
public class JsonProductConverted : JsonTypeInferringConverterBase
{
protected override Type InferType(Type objectType, JObject json)
{
//var type = GetTypeFromId((int) json["typeId"]); // Construct type from field in
return typeof(ProductImpl);
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
public abstract class JsonTypeInferringConverterBase : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
protected abstract Type InferType(Type objectType, JObject json);
protected virtual object CreateObject(Type actualType, JsonSerializer serializer, JObject json)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(actualType);
return contract.DefaultCreator();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var json = JObject.Load(reader);
var actualType = InferType(objectType, json);
// Construct object (or reuse existingValue if compatible)
if (existingValue == null || !actualType.IsAssignableFrom(existingValue.GetType()))
{
existingValue = CreateObject(actualType, serializer, json);
}
// Populate object.
using (var subReader = json.CreateReader())
{
serializer.Populate(subReader, existingValue);
}
return existingValue;
}
}
Note that the concrete objects must have parameterless constructors for this to work. If not, you can override protected virtual object CreateObject(Type actualType, JsonSerializer serializer, JObject json) and manually invoke a parameterized constructor by deserializing select properties inside the JObject json.
Sample fiddle #2.
I have a situation where I need to serialize out any GameObject as something else internally (GameObjectMetadata) that contains data helpful to deserializing it again later.
public struct GameObjectMetadata
{
public string AssetFullPath;
public string AssetBundle;
public string GUID;
public string UnityType;
}
public struct TestPrefabLink
{
public GameObject Prefab;
}
I'm really struggling to implement the JSON.NET converters required to make this happen. I've been at it awhile and have come up with the below code. It mostly works, but I get the following error:
JsonSerializationException: Unexpected token while deserializing
object: EndObject. Path 'Components.$values[0]', line 14, position 7.
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal
(Newtonsoft.Json.JsonReader reader, System.Type objectType,
Newtonsoft.Json.Serialization.JsonContract contract,
Newtonsoft.Json.Serialization.JsonProperty member,
Newtonsoft.Json.Serialization.JsonContainerContract containerContract,
Newtonsoft.Json.Serialization.JsonProperty containerMember,
System.Object existingValue) (at
C:/Development/Releases/Json/Working/Newtonsoft.Json/Working-Signed/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs:336)
Serializer:
data = JsonConvert.SerializeObject(blueprint, Formatting.Indented,
new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
NullValueHandling = NullValueHandling.Include,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Converters = new List<JsonConverter>() { new GameObjectMetadataJsonConverter(), new UnityObjectJsonConverter() }
});
Converters used:
public class GameObjectMetadataJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var jobj = JObject.FromObject(value);
jobj.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var metadata = serializer.Deserialize<GameObjectMetadata>(reader);
// Snipped - Code here successfully converts to GameObject
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof (GameObjectMetadata);
}
}
public class UnityObjectJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(UnityEngine.Object).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return existingValue; // This should not be called. But it is??
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var metadata = new GameObjectMetadata();
metadata.UnityType = value.GetType().ToString();
var path = UnityEditor.AssetDatabase.GetAssetPath((UnityEngine.Object)value);
var bundleName = UnityEditor.AssetImporter.GetAtPath(path).assetBundleName;
var guid = UnityEditor.AssetDatabase.AssetPathToGUID(path);
metadata.AssetBundle = bundleName;
metadata.AssetFullPath = path;
metadata.GUID = guid;
//var jobj = JObject.FromObject(metadata);
//jobj.WriteTo(writer);
serializer.Serialize(writer, metadata);
}
}
It seems whatever I'm doing here is causing some bad JSON write out. But I cannot figure out how else to write out a different struct/class from the expected one.
UnityObjectJsonConverter.ReadJson() will indeed be called because, during deserialization, CanConvert(objectType) will be called with the expected type to be deserialized not the actual type serialized to the file. And the expected type will be some subtype of UnityEngine.Object.
Thus you need to do something like:
public struct GameObjectMetadata
{
public GameObjectMetadata(UnityEngine.Object value)
{
if (value == null)
throw new ArgumentNullException();
this.UnityType = value.GetType();
var path = UnityEditor.AssetDatabase.GetAssetPath(value);
var bundleName = UnityEditor.AssetImporter.GetAtPath(path).assetBundleName;
var guid = UnityEditor.AssetDatabase.AssetPathToGUID(path);
this.AssetBundle = bundleName;
this.AssetFullPath = path;
this.GUID = guid;
}
public UnityEngine.Object GetRealObject()
{
// I'm not a unity3d developer so I am not sure this is the correct method to call.
return UnityEditor.AssetDatabase.LoadAssetAtPath(AssetFullPath, UnityType);
}
public string AssetFullPath;
public string AssetBundle;
public string GUID;
public Type UnityType;
}
public class UnityObjectJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(UnityEngine.Object).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var metadata = serializer.Deserialize<GameObjectMetadata>(reader);
return metadata.GetRealObject();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is never called when value == null. Instead Json.NET calls writer.WriteNull() directly.
var metadata = new GameObjectMetadata((UnityEngine.Object)value);
serializer.Serialize(writer, metadata);
}
}
There is no need for GameObjectMetadataJsonConverter.
Note I'm not a unity3d developer so I am not sure AssetDatabase.LoadAssetAtPath(AssetFullPath, Type.GetType(UnityType)) is the correct method to call to reconstruct a game object from its metadata, so replace with whatever is correct.
I met a situation as below could anybody help me achieve as below?
For Example, if I have the class:-
public class Sample
{
public String name {get;set;}
public MyClass myclass {get;set;}
}
My Myclass will be as follow:
public class MyClass
{
public String p1 {get;set;}
public String p2 {get;set;}
}
When I am using Json.net to Serialize the object of the class Sample,I got as below and it works well.
{
"name":"...",
"myclass":
{
"p1":"...",
"p2":"..."
}
}
Its correct but I wonder is it possible to get the json string as below?
{
"name":"...",
"p1":"...",
"p2":"..."
}
You can create anonymous object and serialize it:
var sample = new Sample {
name = "Bob",
myclass = new MyClass {
p1 = "x",
p2 = "y"
}};
string json = JsonConvert.SerializeObject(new {
sample.name,
sample.myclass.p1,
sample.myclass.p2
});
Result
{"name":"Bob","p1":"x","p2":"y"}
But I suggest you either use default serialization of your Sample class, or create class which will be serialized into your format (i.e. move MyClass properties into Sample).
UPDATE: You can use custom converter, which flattens object and serializes all inner objects properties as top level object properties:
public class FlattenJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}
JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}
private void WriteJson(JsonWriter writer, JObject value)
{
foreach (var p in value.Properties())
{
if (p.Value is JObject)
WriteJson(writer, (JObject)p.Value);
else
p.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true; // works for any type
}
}
Usage:
string json = JsonConvert.SerializeObject(sample, new FlattenJsonConverter());
Or you can simply hide anonymous type creation into custom converter, if you need this behavior for one type only:
public class SampleJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
Sample sample = (Sample)value;
JToken t = JToken.FromObject(new {
sample.name,
sample.myclass.p1,
sample.myclass.p2
});
t.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Sample);
}
}
Is there some way to set what the default representation for null values should be in Json.NET?
More specifically null values inside an array.
Given the class
public class Test
{
public object[] data = new object[3] { 1, null, "a" };
}
Then doing this
Test t = new Test();
string json = JsonConvert.SerializeObject(t);
Gives
{"data":[1,null,"a"]}
Is it possible to make it look like this?
{"data":[1,,"a"]}
Without using string.Replace.
Figured it out. I had to implement a custom JsonConverter.
As others mentioned this will not produce valid/standard Json.
public class ObjectCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(object[]);
}
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)
{
object[] collection = (object[])value;
writer.WriteStartArray();
foreach (var item in collection)
{
if (item == null)
{
writer.WriteRawValue(""); // This procudes "nothing"
}
else
{
writer.WriteValue(item);
}
}
writer.WriteEndArray();
}
}
Use it like this
Test t = new Test();
string json = JsonConvert.SerializeObject(t, new ObjectCollectionConverter());