NB: I am using System.Text.Json not JSON.NET for which there is similar question that has been answered. I need the same answer but for System.Text.Json. I would like to write my class to Json and ensure that it is human readable. To do this I have set the indent to true in options. However, the class contains a List<double> property which I don't want to indent as it makes the file very long.
So I have this:
public class ThingToSerialize
{
public string Name {get;set;}
//other properties here
public List<double> LargeList {get;set;}
};
var thing = new ThingToSerialize {Name = "Example", LargeList = new List<double>{0,0,0}};
var options = new JsonSerializerOptions
{
WriteIndented = true
};
options.Converters.Add(new DontIndentArraysConverter());
var s = JsonSerializer.Serialize(thing, options);
and I want it to serialize like this:
{
"Name": "Example",
"LargeList ": [0,0,0]
}
Not this (or something along these lines):
{
"Name": "Example",
"LargeList ": [
0,
0,
0
]
}
I have written a JsonConverter to achieve this:
public class DontIndentArraysConverter : JsonConverter<List<double>>
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<double>);
}
public override List<double> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<List<double>>(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, List<double> value, JsonSerializerOptions options)
{
var s = JsonSerializer.Serialize(value);
writer.WriteStringValue(s);
}
}
However this writes the array as a string which I don't really want. Whats the best approach for this?
i.e. you get "[1,2,3]" instead of [1,2,3]
Secondly, the writer object that is passed into the Write function has an Options property but this cannot be changed. So if I write the array out manually using the writer object, it is indented.
Thanks for your idea, I wrote a wrapper based on the Converter hint
The idea is write a temp value during conversion, put correct array to a temp dict and replace them later
It's a bit late for you but maybe it can help other guys
public class CustomSerializer : IDisposable
{
private readonly Dictionary<string, string> _replacement = new Dictionary<string, string>();
public string Serialize<T>(T obj)
{
var converterForListInt = new DontIndentArraysConverterForListInt(_replacement);
var options = new JsonSerializerOptions
{
IgnoreNullValues = true,
WriteIndented = true
};
options.Converters.Add(converterForListInt);
var json = JsonSerializer.Serialize(obj, options);
foreach (var (k, v) in _replacement)
json = json.Replace(k, v);
return json;
}
public void Dispose()
{
_replacement.Clear();
}
public class DontIndentArraysConverterForListInt : JsonConverter<List<int>>
{
private readonly Dictionary<string, string> _replacement;
public DontIndentArraysConverterForListInt(Dictionary<string, string> replacement)
{
_replacement = replacement;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<int>);
}
public override List<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<List<int>>(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, List<int> value, JsonSerializerOptions options)
{
if (value.Count > 0)
{
var key = $"TMP_{Guid.NewGuid().ToString()}";
var sb = new StringBuilder();
sb.Append('[');
foreach (var i in value)
{
sb.Append(i);
sb.Append(',');
}
sb.Remove(sb.Length - 1, 1); // trim last ,
sb.Append(']');
_replacement.Add($"\"{key}\"", sb.ToString());
//
writer.WriteStringValue(key);
}
else
{
// normal
writer.WriteStartArray();
writer.WriteEndArray();
}
}
}
}
Encountered this old question while trying to do the same thing for a List<string>. With .NET 6 (or at least package System.Text.Json v6.0.0.0) and later, this is now possible directly with System.Text.Json and the Utf8JsonWriter method WriteRawValue.
class ListDoubleSingleLineConverter : JsonConverter<List<double>>
{
//Read override omitted
public override void Write(Utf8JsonWriter writer, List<double> value, JsonSerializerOptions options)
{
writer.WriteRawValue(
String.Concat(
"[ ",
// Depending on your values, you might want to use LINQ .Select() and String.Format() to control the output
String.Join(", ", value),
" ]"));
}
}
Of note from the documentation:
When using this method, the input content will be written to the writer destination as-is, unless validation fails (when it is enabled).
(For my List<string> case, this meant transforming value with value.Select(v => String.Concat("\"", v, "\"")) before String.Join, as otherwise the strings were emitted unquoted; when using WriteRawValue you assume responsibility for the supplied argument being a well-formed JSON fragment.)
Tested with:
JsonSerializerOptions options = new JsonSerializerOptions()
{
WriteIndented = true,
Converters = { new ListDoubleSingleLineConverter() },
};
var test = new
{
Name = "Example",
LargeList = new List<double>() { 1.0, 1.1, 1.2, 1.3 },
OtherProperty = 27.0,
};
Console.WriteLine(JsonSerializer.Serialize(test, options));
Output:
{
"Name": "Example",
"LargeList": [ 1, 1.1, 1.2, 1.3 ],
"OtherProperty": 27
}
Related
This question already has answers here:
System.Text.Json - Deserialize nested object as string
(3 answers)
Closed last year.
So i am in a situation where my NewtonJson custom converters do not work with from body api calls. ( its using System.Text.Json by default to convert).
so currently i had a temporary solution to write some wrappers which will ultimately call Newtonjson until the text.json converters are written and tested.
what i want to do is to read the entire object as a string and pass it along to newtons converter
my StartUp.cs
services.AddControllers(options =>
options.Filters.Add<ApiExceptionFilterAttribute>())
.AddFluentValidation(x => x.AutomaticValidationEnabled = false)//ValidationBehaviour handle fluent validations.
.AddJsonOptions(x =>
{
x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
x.JsonSerializerOptions.Converters.Add(new SystemModelConverter());
x.JsonSerializerOptions.IgnoreNullValues = true;
});
my converter class
public class SystemModelConverter : JsonConverter<ISystemModel>
{
public override bool CanConvert(Type typeToConvert)
{
return (typeToConvert.GetInterface(typeof(ISystemModel).Name) != null);
}
public override ISystemModel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = new StringBuilder();
while(reader.Read())
{
var str = reader.GetString();
value.Append(str);
}
//passing the string along
return Newtonsoft.Json.JsonConvert.DeserializeObject(value.ToString(), CoreExtensions.GetJsonSerializerSettings()) as ISystemModel;
}
public override void Write(Utf8JsonWriter writer, ISystemModel value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
looks this this worked
using (var jsonDocument = JsonDocument.ParseValue(ref reader))
{
var jsonText = jsonDocument.RootElement.GetRawText();
}
Source
I have a json from here https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0 which looks like:
{
"782": {
"First_UTC": "2021-02-06T17:08:11Z",
"Last_UTC": "2021-02-07T17:47:46Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 721.77,
"ct": 113450,
"mn": 698.8193,
"mx": 742.2686
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"783": {
"First_UTC": "2021-02-07T17:47:46Z",
"Last_UTC": "2021-02-08T18:27:22Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 722.186,
"ct": 107270,
"mn": 698.7664,
"mx": 743.1983
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"sol_keys": [ "782", "783" ],
"validity_checks": { /* Some complex object */ }
}
I need only part of this information so I have created the following classes:
public class MarsWheather {
[JsonPropertyName("First_UTC")]
public DateTime FirstUTC { get; set; }
[JsonPropertyName("Last_UTC")]
public DateTime LastUTC { get; set; }
[JsonPropertyName("Season")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public Season MarsSeason { get; set; }
[JsonPropertyName("PRE")]
public DataDescription AtmosphericPressure { get; set; }
}
public enum Season {
winter,
spring,
summer,
autumn
}
public class DataDescription{
[JsonPropertyName("av")]
public double Average { get; set; }
[JsonPropertyName("ct")]
public double TotalCount { get; set; }
[JsonPropertyName("mn")]
public double Minimum { get; set; }
[JsonPropertyName("mx")]
public double Maximum { get; set; }
}
The problem is that the JSON root object from NASA contains properties "validity_checks" and "sol_keys" that I don't need and want to skip. In Newton.Json I've used JObject.Parse to do this, but in System.Text.Json I want to use
JsonSerializer.DeserializeAsync<Dictionary<string, MarsWheather>>(stream, new JsonSerializerOptions { IgnoreNullValues = true });
Unfortunately, when I do I get an exception:
System.Text.Json.JsonException: The JSON value could not be converted to MarsWheather. Path: $.sol_keys | LineNumber: 120 | BytePositionInLine: 15.
Demo fiddle here.
Is it possible?
Your JSON root object consists of certain fixed keys ("sol_keys" and "validity_checks") whose values each have some fixed schema, and any number of variable keys (the "782" numeric keys) whose values all share a common schema that differs from the schemas of the fixed key values:
{
"782": {
// Properties corresponding to your MarsWheather object
},
"783": {
// Properties corresponding to your MarsWheather object
},
// Other variable numeric key/value pairs corresponding to KeyValuePair<string, MarsWheather>
"sol_keys": [
// Some array values you don't care about
],
"validity_checks": {
// Some object you don't care about
}
}
You would like to deserialize just the variable keys, but when you try to deserialize to a Dictionary<string, MarsWheather> you get an exception because the serializer tries to deserialize a fixed key value as if it were variable key value -- but since the fixed key has an array value while the variable keys have object values, an exception gets thrown. How can System.Text.Json be told to skip the known, fixed keys rather than trying to deserialize them?
If you want to deserialize just the variable keys and skip the fixed, known keys, you will need to create a custom JsonConverter. The easiest way to do that would be to first create some root object for your dictionary:
[JsonConverter(typeof(MarsWheatherRootObjectConverter))]
public class MarsWheatherRootObject
{
public Dictionary<string, MarsWheather> MarsWheathers { get; } = new Dictionary<string, MarsWheather>();
}
And then define the following converter for it as follows:
public class MarsWheatherRootObjectConverter : FixedAndvariablePropertyNameObjectConverter<MarsWheatherRootObject, Dictionary<string, MarsWheather>, MarsWheather>
{
static readonly Dictionary<string, ReadFixedKeyMethod> FixedKeyReadMethods = new Dictionary<string, ReadFixedKeyMethod>(StringComparer.OrdinalIgnoreCase)
{
{ "sol_keys", (ref Utf8JsonReader reader, MarsWheatherRootObject obj, string name, JsonSerializerOptions options) => reader.Skip() },
{ "validity_checks", (ref Utf8JsonReader reader, MarsWheatherRootObject obj, string name, JsonSerializerOptions options) => reader.Skip() },
};
protected override Dictionary<string, MarsWheather> GetDictionary(MarsWheatherRootObject obj) => obj.MarsWheathers;
protected override void SetDictionary(MarsWheatherRootObject obj, Dictionary<string, MarsWheather> dictionary) => throw new RowNotInTableException();
protected override bool TryGetFixedKeyReadMethod(string name, JsonSerializerOptions options, out ReadFixedKeyMethod method) => FixedKeyReadMethods.TryGetValue(name, out method);
protected override IEnumerable<KeyValuePair<string, WriteFixedKeyMethod>> GetFixedKeyWriteMethods(JsonSerializerOptions options) => Enumerable.Empty<KeyValuePair<string, WriteFixedKeyMethod>>();
}
public abstract class FixedAndvariablePropertyNameObjectConverter<TObject, TDictionary, TValue> : JsonConverter<TObject>
where TDictionary : class, IDictionary<string, TValue>, new()
where TObject : new()
{
protected delegate void ReadFixedKeyMethod(ref Utf8JsonReader reader, TObject obj, string name, JsonSerializerOptions options);
protected delegate void WriteFixedKeyMethod(Utf8JsonWriter writer, TObject value, JsonSerializerOptions options);
protected abstract TDictionary GetDictionary(TObject obj);
protected abstract void SetDictionary(TObject obj, TDictionary dictionary);
protected abstract bool TryGetFixedKeyReadMethod(string name, JsonSerializerOptions options, out ReadFixedKeyMethod method);
protected abstract IEnumerable<KeyValuePair<string, WriteFixedKeyMethod>> GetFixedKeyWriteMethods(JsonSerializerOptions options);
public override TObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return (typeToConvert.IsValueType && Nullable.GetUnderlyingType(typeToConvert) == null)
? throw new JsonException(string.Format("Unepected token {0}", reader.TokenType))
: default(TObject);
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException(string.Format("Unepected token {0}", reader.TokenType));
var obj = new TObject();
var dictionary = GetDictionary(obj);
var valueConverter = (typeof(TValue) == typeof(object) ? null : (JsonConverter<TValue>)options.GetConverter(typeof(TValue))); // Encountered a bug using the builtin ObjectConverter
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
var name = reader.GetString();
reader.ReadAndAssert();
if (TryGetFixedKeyReadMethod(name, options, out var method))
{
method(ref reader, obj, name, options);
}
else
{
if (dictionary == null)
SetDictionary(obj, dictionary = new TDictionary());
dictionary.Add(name, valueConverter.ReadOrDeserialize(ref reader, typeof(TValue), options));
}
}
else if (reader.TokenType == JsonTokenType.EndObject)
{
return obj;
}
else
{
throw new JsonException(string.Format("Unepected token {0}", reader.TokenType));
}
}
throw new JsonException(); // Truncated file
}
public override void Write(Utf8JsonWriter writer, TObject value, JsonSerializerOptions options)
{
writer.WriteStartObject();
var dictionary = GetDictionary(value);
if (dictionary != null)
{
var valueConverter = (typeof(TValue) == typeof(object) ? null : (JsonConverter<TValue>)options.GetConverter(typeof(TValue))); // Encountered a bug using the builtin ObjectConverter
foreach (var pair in dictionary)
{
// TODO: handle DictionaryKeyPolicy
writer.WritePropertyName(pair.Key);
valueConverter.WriteOrSerialize(writer, pair.Value, typeof(TValue), options);
}
}
foreach (var pair in GetFixedKeyWriteMethods(options))
{
writer.WritePropertyName(pair.Key);
pair.Value(writer, value, options);
}
writer.WriteEndObject();
}
}
public static partial class JsonExtensions
{
public static void WriteOrSerialize<T>(this JsonConverter<T> converter, Utf8JsonWriter writer, T value, Type type, JsonSerializerOptions options)
{
if (converter != null)
converter.Write(writer, value, options);
else
JsonSerializer.Serialize(writer, value, type, options);
}
public static T ReadOrDeserialize<T>(this JsonConverter<T> converter, ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> converter != null ? converter.Read(ref reader, typeToConvert, options) : (T)JsonSerializer.Deserialize(ref reader, typeToConvert, options);
public static void ReadAndAssert(this ref Utf8JsonReader reader)
{
if (!reader.Read())
throw new JsonException();
}
}
And now you will be able to deserialize to MarsWheatherRootObject as follows:
var root = await System.Text.Json.JsonSerializer.DeserializeAsync<MarsWheatherRootObject>(
stream,
new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
Demo fiddle #1 here.
Notes:
FixedAndvariablePropertyNameObjectConverter<TObject, TDictionary, TValue> provides a general framework for serializing and deserializing objects with fixed and variable properties. If later you decide to deserialize e.g. "sol_keys", you could modify MarsWheatherRootObject as follows:
[JsonConverter(typeof(MarsWheatherRootObjectConverter))]
public class MarsWheatherRootObject
{
public Dictionary<string, MarsWheather> MarsWheathers { get; } = new Dictionary<string, MarsWheather>();
public List<string> SolKeys { get; set; } = new List<string>();
}
And the converter as follows:
public class MarsWheatherRootObjectConverter : FixedAndvariablePropertyNameObjectConverter<MarsWheatherRootObject, Dictionary<string, MarsWheather>, MarsWheather>
{
static readonly Dictionary<string, ReadFixedKeyMethod> FixedKeyReadMethods = new(StringComparer.OrdinalIgnoreCase)
{
{ "sol_keys", (ref Utf8JsonReader reader, MarsWheatherRootObject obj, string name, JsonSerializerOptions options) =>
{
obj.SolKeys = JsonSerializer.Deserialize<List<string>>(ref reader, options);
}
},
{ "validity_checks", (ref Utf8JsonReader reader, MarsWheatherRootObject obj, string name, JsonSerializerOptions options) => reader.Skip() },
};
static readonly Dictionary<string, WriteFixedKeyMethod> FixedKeyWriteMethods = new Dictionary<string, WriteFixedKeyMethod>()
{
{ "sol_keys", (w, v, o) =>
{
JsonSerializer.Serialize(w, v.SolKeys, o);
}
},
};
protected override Dictionary<string, MarsWheather> GetDictionary(MarsWheatherRootObject obj) => obj.MarsWheathers;
protected override void SetDictionary(MarsWheatherRootObject obj, Dictionary<string, MarsWheather> dictionary) => throw new RowNotInTableException();
protected override bool TryGetFixedKeyReadMethod(string name, JsonSerializerOptions options, out ReadFixedKeyMethod method) => FixedKeyReadMethods.TryGetValue(name, out method);
protected override IEnumerable<KeyValuePair<string, WriteFixedKeyMethod>> GetFixedKeyWriteMethods(JsonSerializerOptions options) => FixedKeyWriteMethods;
}
Demo fiddle #2 here.
I have a class that looks like this:
public class Resource<TState>
{
public TState State { get; set; }
}
The resulting JSON would normally be something like this
{
"state": {
"prop1": "foo",
"prop2": "bar"
}
}
However I need the JSON to look like this:
{
"prop1": "foo",
"prop2": "bar"
}
And I need this to work with serializing and deserializing.
Is there a way to achieve this using System.Text.Json?
If you can't serialize/deserialize the State property directly, then you could implement a generic JsonConverter that must be instatiated for every possible type you may use for TState, and add those to a JsonSerializerOptions object. Obviously only works if you know what types you will use for TState.
public class Resource<TState>
{
public TState State { get; set; }
}
public class ResourceJsonConverter<TState> : JsonConverter<Resource<TState>>
{
public override Resource<TState> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new Resource<TState>()
{
State = JsonSerializer.Deserialize<TState>(ref reader, options)
};
public override void Write(Utf8JsonWriter writer, Resource<TState> value, JsonSerializerOptions options)
=> JsonSerializer.Serialize<TState>(writer, value.State, options);
}
[Fact]
public void Convert()
{
var r = new Resource<string> { State = "test" };
var options = new JsonSerializerOptions();
options.Converters.Add(new ResourceJsonConverter<string>());
JsonSerializer.Serialize(r, options).Should().Be("test");
}
I think this could work:
string jsonString = JsonSerializer.Serialize(a.State);
var state = JsonSerializer.Deserialize<TState>(jsonString);
var b = new Resource<TState>
{
State = state
};
I am receiving data that looks like this from an online service provider:
{
name: "test data",
data: [
[ "2017-05-31", 2388.33 ],
[ "2017-04-30", 2358.84 ],
[ "2017-03-31", 2366.82 ],
[ "2017-02-28", 2329.91 ]
],
}
I would like to parse it into an object that looks like this:
public class TestData
{
public string Name;
public List<Tuple<DateTime, double>> Data;
}
The only thing I have been able to find is how to parse an array of objects into a list of tulples, for example: Json.NET deserialization of Tuple<...> inside another type doesn't work?
Is there a way to write a custom converter that would handle this?
If anyone is interested in a more generic solution for ValueTuples
public class TupleConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var type = value.GetType();
var array = new List<object>();
FieldInfo fieldInfo;
var i = 1;
while ((fieldInfo = type.GetField($"Item{i++}")) != null)
array.Add(fieldInfo.GetValue(value));
serializer.Serialize(writer, array);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var argTypes = objectType.GetGenericArguments();
var array = serializer.Deserialize<JArray>(reader);
var items = array.Select((a, index) => a.ToObject(argTypes[index])).ToArray();
var constructor = objectType.GetConstructor(argTypes);
return constructor.Invoke(items);
}
public override bool CanConvert(Type type)
{
return type.Name.StartsWith("ValueTuple`");
}
}
Usage is as follows:
var settings = new JsonSerializerSettings();
settings.Converters.Add(new TupleConverter());
var list = new List<(DateTime, double)>
{
(DateTime.Now, 7.5)
};
var json = JsonConvert.SerializeObject(list, settings);
var result = JsonConvert.DeserializeObject(json, list.GetType(), settings);
Rather than use tuples, I would create a class that is specific to the task. In this case your JSON data comes in as a list of lists of strings which is a bit awkward to deal with. One method would be to deserialise as List<List<string>> and then convert afterwards. For example, I would go with 3 classes like this:
public class IntermediateTestData
{
public string Name;
public List<List<string>> Data;
}
public class TestData
{
public string Name;
public IEnumerable<TestDataItem> Data;
}
public class TestDataItem
{
public DateTime Date { get; set; }
public double Value { get; set; }
}
Now deserialise like this:
var intermediate = JsonConvert.DeserializeObject<IntermediateTestData>(json);
var testData = new TestData
{
Name = intermediate.Name,
Data = intermediate.Data.Select(d => new TestDataItem
{
Date = DateTime.Parse(d[0]),
Value = double.Parse(d[1])
})
};
So using JSON.NET LINQ, I managed to get it to work as you prescribed...
var result = JsonConvert.DeserializeObject<JObject>(json);
var data = new TestData
{
Name = (string)result["name"],
Data = result["data"]
.Select(t => new Tuple<DateTime, double>(DateTime.Parse((string)t[0]), (double)t[1]))
.ToList()
};
This is the full test I wrote
public class TestData
{
public string Name;
public List<Tuple<DateTime, double>> Data;
}
[TestMethod]
public void TestMethod1()
{
var json =
#"{
name: ""test data"",
data: [
[ ""2017-05-31"", 2388.33 ],
[ ""2017-04-30"", 2358.84 ],
[ ""2017-03-31"", 2366.82 ],
[ ""2017-02-28"", 2329.91 ]
],
}";
var result = JsonConvert.DeserializeObject<JObject>(json);
var data = new TestData
{
Name = (string)result["name"],
Data = result["data"]
.Select(t => new Tuple<DateTime, double>(DateTime.Parse((string)t[0]), (double)t[1]))
.ToList()
};
Assert.AreEqual(2388.33, data.Data[0].Item2);
}
However, while this may work, I am in agreement with the rest of the comments/answers that using tuples for this is probably not the correct way to go. Using concrete POCO's is definitely going to be a hell of a lot more maintainable in the long run simply because of the Item1 and Item2 properties of the Tuple<,>.
They are not the most descriptive...
I took the generic TupleConverter from here: Json.NET deserialization of Tuple<...> inside another type doesn't work?
And made a generic TupleListConverter.
Usage:
public class TestData
{
public string Name;
[Newtonsoft.Json.JsonConverter(typeof(TupleListConverter<DateTime, double>))]
public List<Tuple<DateTime, double>> Data;
}
public void Test(string json)
{
var testData = JsonConvert.DeserializeObject<TestData>(json);
foreach (var tuple in testData.data)
{
var dateTime = tuple.Item1;
var price = tuple.Item2;
... do something...
}
}
Converter:
public class TupleListConverter<U, V> : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Tuple<U, V>) == objectType;
}
public override object ReadJson(
Newtonsoft.Json.JsonReader reader,
Type objectType,
object existingValue,
Newtonsoft.Json.JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
return null;
var jArray = Newtonsoft.Json.Linq.JArray.Load(reader);
var target = new List<Tuple<U, V>>();
foreach (var childJArray in jArray.Children<Newtonsoft.Json.Linq.JArray>())
{
var tuple = new Tuple<U, V>(
childJArray[0].ToObject<U>(),
childJArray[1].ToObject<V>()
);
target.Add(tuple);
}
return target;
}
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
I have this kind of class:
class Thing
{
// ...
public IDictionary<string, dynamic> Parameters { get; set; }
}
//...
void Test()
{
Thing thing = new Thing();
thing.Paramaters["counting"] = new List<int>{1,2,3};
thing.Parameters["name"] = "Numbers";
thing.Parameters["size"] = 3;
string result = JsonConvert.SerializeObject(thing);
}
And I'd like to result to contain this:
{
"Parameters" :
{
"counting" : "[1,2,3]",
"name" : "Numbers",
"size" : 3
}
}
I have taken a look at IContractResolver and believe I should special case strings on deserialization to check if there's JSON in them, and special case all class objects to convert them to a string. I just have no idea where to begin doing that.
In the end the problem is this: the data structure I'm plugging this JSON into does not work with nested JSON. It only knows about the basic data, i.e. string and numbers, at this "sublevel". I know, terrible, get rid of this evil data structure. Well, I can't. So I need to be creative and I thought this might be a way out. If anyone can think of a better way, I'd much appreciate it!
EDIT The answers below special case the List<int> example I put in Test, but it's still a Dictionary<string, dynamic> which can contain everything. That's what I mean with nested JSON: any JSON, not just an array.
I understand the problem is not so much about writing a List to a string, but more about a way to create JSON with only two levels of depth - having everything beyond that a string.
I don't think an IContractResolver would work for this, you should implement a JsonConverter instead. The basic idea would be that it iterates over your object's children, then over their children, checking their type. If they're an array or an object - it would replace them with a serialized string.
Implementation:
class TwoDepthJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var jo = JObject.FromObject(value);
foreach (var property in jo)
{
foreach (var parameter in property.Value)
{
var paramVal = parameter.First;
if (paramVal.Type == JTokenType.Array || paramVal.Type == JTokenType.Object)
{
paramVal.Replace(JsonConvert.SerializeObject(paramVal.ToObject<object>()));
}
}
}
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JToken.ReadFrom(reader).ToObject(objectType);
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
Usage:
Thing thing = new Thing();
thing.Parameters["counting"] = new List<int> { 1, 2, 3 };
thing.Parameters["name"] = "Numbers";
thing.Parameters["size"] = 3;
string result = JsonConvert.SerializeObject(thing, Formatting.Indented, new TwoDepthJsonConverter());
// Results:
// {
// "Parameters": {
// "counting": "[1,2,3]",
// "name": "Numbers",
// "size": 3
// }
// }
Of course, performance could be improved - for example writing to the writer manually instead of parsing to a JObject and then manipulating it. However this should be a good starting point.
You can create a JsonConverter and instead of using List<T> you would use a custom class that inherits from List<T>..
The reason for it needs to be a custom List is that if you register a JsonConverter for List<T> you would not be able to get normal array serialization.
Following http://blog.maskalik.com/asp-net/json-net-implement-custom-serialization/ I was able to make this.
public class CustomListSerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var values = (IEnumerable)value;
var items = values.Cast<object>().ToList();
var s = JsonConvert.SerializeObject(items);
writer.WriteValue(s);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(IEnumerable));
}
}
[JsonConverter(typeof(CustomListSerializer))]
internal class CustomList<T> : List<T>
{
}
class Program
{
static void Main(string[] args)
{
var parameters = new Dictionary<string, object>();
parameters.Add("counting", new CustomList<int>() { 1, 2, 3, 5 });
parameters.Add("users", new CustomList<User>() { new User { Name = "TryingToImprove" }, new User { Name = "rubenvb" } });
parameters.Add("name", "Numbers");
parameters.Add("size", 4);
var thing = new
{
Parameters = parameters,
Name = "THING",
Test = new List<int>() { 1, 2, 3}
};
Console.WriteLine(JsonConvert.SerializeObject(thing));
}
}
internal class User
{
public string Name { get; set; }
}
which will return
{
"Parameters": {
"counting": "[1,2,3,5]",
"users": "[{\"Name\":\"TryingToImprove\"},{\"Name\":\"rubenvb\"}]",
"name": "Numbers",
"size": 4
},
"Name": "THING",
"Test": [1, 2, 3]
}
This is working -
internal class CustomJsonFormatter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(Thing));
}
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)
{
var data = value as Thing;
foreach (var prop in data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
writer.WriteStartObject();
writer.WritePropertyName(prop.Name);
writer.WriteStartObject();
var local = prop.GetValue(data, null) as Dictionary<string, object>;
foreach (var key in local.Keys)
{
writer.WritePropertyName(key);
if (local[key].GetType() == typeof(List<int>))
{
string s = "[";
var arr = ((List<int>)local[key]);
for (var i = 0; i < arr.Count; i++)
{
s += arr[i].ToString() + ",";
}
writer.WriteValue(s.Substring(0, s.Length - 1) + "]");
}
else { writer.WriteValue(local[key]); }
}
}
writer.WriteEndObject();
writer.WriteEndObject();
}
var settings = new JsonSerializerSettings();
settings.Converters.Add(new CustomJsonFormatter());
string result = JsonConvert.SerializeObject(thing, settings);
produces
{"Parameters":{"counting":"[1,2,3]","name":"Numbers","size":3}}