This question already has an answer here:
Efficiently get full json string in JsonConverter.ReadJson()
(1 answer)
Closed 2 years ago.
I'm reading a large object which contains thousands of sub-objects that I don't want to deserialize into full C# objects (for performance reasons) but just load them as dummy wrapper objects to keep their JSON as strings (for later phase where they are serialized again).
So far I have the following concept code (not final), but I would prefer to avoid the JObject creation and just read somehow the sub-objects string straight from the reader into the wrapper json member string.
Is there an easy way to do so without switching over all possible tokens and reading them one by one?
namespace Playground
{
[JsonConverter(typeof(ObjectJsonWrapperConverter))]
public class ObjectJsonWrapper
{
public string Json;
}
public class ObjectJsonWrapperConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(((ObjectJsonWrapper)value).Json);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var wrapper=new ObjectJsonWrapper();
if (reader.TokenType == JsonToken.Null)
{
return null;
}
else if (reader.TokenType == JsonToken.StartObject)
{
var obj=JsonConvert.SerializeObject(JObject.Load(reader));
wrapper.Json = obj;
return wrapper;
}
else
{
throw new ArgumentException("Bad parsing");
}
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
}
Already saw the other question but it doesn't answer my needs
If I understand you correctly, what you are looking for is persisting content of JsonReader as string, and that's what JRaw does.
class ObjectJsonWrapperConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => true;
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
// This creates a raw JSON string without parsing it.
var raw = JRaw.Create(reader);
var obj = new ObjectJsonWrapper();
obj.Json = (string)raw.Value;
return obj;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use it:
public class MyObject
{
public ObjectJsonWrapper LargeObject { get; set; }
}
[JsonConverter(typeof(ObjectJsonWrapperConverter))]
public class ObjectJsonWrapper
{
public string Json { get; set; }
}
var json = #"
{
""largeObject"": {
""value"": ""some value""
}
}
";
var obj = JsonConvert.DeserializeObject<MyObject>(json);
Related
I thought this would pretty easily to convert from JSON array to a comma-separated string and back using Newtonsoft, but I am having issues getting the ReadJson to work. I thought I would just deserialize from the reader to a string array and then call Join, but I keep getting errors: Unexpected token while deserializing object: PropertyName. Path '[0]..
Here is the code I am using:
public class myModel
{
[JsonConverter(typeof(CommaSeperatedStringJsonConverter))]
public string myString { get; set; }
public int myNumber { get; set; }
}
public class CommaSeperatedStringJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var stringArray = serializer.Deserialize(reader, typeof(string[]));
return string.Join(",", stringArray);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is null)
{
writer.WriteNull();
}
else
{
serializer.Serialize(writer, value.ToString().Split(','), typeof(string[]));
}
}
}
Try using the generic version of Deserialize instead. In other words, change this line:
var stringArray = serializer.Deserialize(reader, typeof(string[]));
To this:
var stringArray = serializer.Deserialize<string[]>(reader);
Fiddle: https://dotnetfiddle.net/KpQSiG
It shouldn't matter, but for some reason it does in this situation. If I have more time I'll try to dig into the source code to see what is happening.
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 try to create custom JSON converter by using code for some answered question.
public class SingleValueArrayConverter<T> : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject)
{
T instance = serializer.Deserialize<T>(reader);
return new List<T>() { instance };
}
else if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<T>>(reader);
}
return null;
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This is my sample model class.
public class Foo
{
[JsonProperty("type")]
public string Type;
}
I use the following code to execute. It causes error about stack overflow.
var converter = new SingleValueArrayConverter<Foo>();
var test1 = "[{ \"type\": \"test\" }]";
var result1 = JsonConvert.DeserializeObject<List<Foo>>(test1, converter);
var test2 = "{ \"type\": \"test\" }";
var result2 = JsonConvert.DeserializeObject<List<Foo>>(test2, converter);
The easiest way to convert any JSON to .NET object is using "ToObject" method of JToken object.
public class FooConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<Foo>>();
}
var item = token.ToObject<Foo>();
return new List<Foo> { item };
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
PS. JToken object is the base class of any JSON data so we can convert JToken object to any appropriated type.
JToken - abstract base class
JContainer - abstract base class of JTokens that can contain other JTokens
JArray - represents an JSON array (contains an ordered list of JTokens)
JObject - represents an JSON object (contains a collection of JProperties)
JProperty - represents a JSON property (a name/JToken pair inside a JObject)
JValue - represents a primitive JSON value (string, number, boolean, null)
I've got some json with huge integers, in the order of a few hundred digits. I'd like to parse those as BouncyCastle's BigInteger (https://github.com/onovotny/BouncyCastle-PCL/blob/pcl/crypto/src/math/BigInteger.cs).
{
"bigNumber":12093812947635091350945141034598534526723049126743245...
}
So I've implemented a converter, using a contract resolver in the default settings.
internal class BigIntegerConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken jToken = JToken.Load(reader);
return new BigInteger(jToken.ToString());
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(BigInteger));
}
}
public class BigIntegerContractResolver : DefaultContractResolver
{
private static readonly JsonConverter bigIntegerConverter = new BigIntegerConverter();
private static Type type = typeof(BigInteger);
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (objectType == type)
{
return bigIntegerConverter;
}
return base.ResolveContractConverter(objectType);
}
}
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
ContractResolver = new BigIntegerContractResolver()
};
The writer works as it should, writing a (large) integer value instead of the class BigInteger with all its properties etc. However, the reading fails. Neither ReadJson nor CanConvert appear to be invoked.
I get the following exception:
JsonReaderException: JSON integer 340597435091750914358634185762341897561435984635897436598435643875643189576413589743659817456... is too large or small for an Int64.
How do I get Json.NET to parse this number as a string instead of an integer?
Ideally I don't want to have to parse the json string myself first, to add quotes.
If your large number isn't quoted, Json.Net will deserialize it as a System.Numerics.BigInteger. This happens inside the JsonTextReader, well before the converter gets a chance to handle it. So if you want your result type to be Org.BouncyCastle.Math.BigInteger, you'll need to convert from System.Numerics.BigInteger. (Seems a little backwards, I know. The other alternative is to create your own JsonTextReader, but that is probably going to be more trouble than it is worth -- most of the useful bits of the existing reader are in private or internal methods, so subclassing it is not practical.)
I was able to get this converter to work:
class BigIntegerConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Org.BouncyCastle.Math.BigInteger));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
System.Numerics.BigInteger big = (System.Numerics.BigInteger)reader.Value;
return new Org.BouncyCastle.Math.BigInteger(big.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(value.ToString());
}
}
Here is the test program I used. Note that I did not use a resolver. JsonSerializerSettings has a Converters collection, so I just added the BigIntegerConverter to that.
class Program
{
static void Main(string[] args)
{
string json = #"
{
""bigNumber"": 12093812947635091350945141034598534526723049126743245
}";
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new BigIntegerConverter() }
};
Foo foo = JsonConvert.DeserializeObject<Foo>(json);
Console.WriteLine(foo.BigNumber.ToString());
}
}
class Foo
{
public Org.BouncyCastle.Math.BigInteger BigNumber { get; set; }
}
Output:
12093812947635091350945141034598534526723049126743245
You can try creating object that handles the output of the json like this:
public class YourModel
{
[JsonConverter(typeof(CustomConverter<BigInteger>))]
public BigInteger YourProperty{ get; set; }
}
And now it can become more generic for every type you need:
public class CustomConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
your code ..
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//explicitly specify the concrete type we want to create
return serializer.Deserialize<T>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
your code ...
}
}
Is it possible in Json.NET to serialize a root-level type in the serialization hierarchy differently than a reference encountered at a lower level in the hierarchy?
For example, with this type
class Serialized {
public Serialized Serialized;
public int A;
}
in this setup
var serialized = new Serialized() { A = 1 };
var serialized2 = new Serialized() { A = 2 };
serialized.Serialized = serialized2;
string json = GetJson(serialized);
where json is
{
"A":1
"Serialized": {
"ref":2
}
}
Specifically, the root-level serialization should use the default serialization strategy and the lower-level ones should use a custom converter (or similar).
You need to define custom JsonConverter and decorate the Serialized property with it:
public class PropertyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
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;
var serialized = (Serialized)value;
writer.WriteStartObject();
writer.WritePropertyName("ref");
writer.WriteValue(serialized.A);
writer.WriteEndObject();
}
}
And your class:
class Serialized {
[JsonConverter(typeof(PropertyConverter))
public Serialized Serialized;
public int A;
}
This will work only for one level of nesting. If you need it to work of multiple nested levels, you need to modify the converter:
public class PropertyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
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;
var serialized = (Serialized)value;
writer.WriteStartObject();
writer.WritePropertyName("ref");
writer.WriteValue(serialized.A);
if(serialized.Serialized != null)
{
writer.WritePropertyName("nested");
writer.WriteValue(JsonConvert.SerializeObject(serialized.Serialized, new JsonConverter[] {new PropertyConverter()}));
}
writer.WriteEndObject();
}
}