You will have to write a custom JsonConverterJsonConverter<List<FieldDifference>>
for this:
public class FieldDifferenceListConverter : JsonConverter<List<FieldDifference>>
{
public override void WriteJson(JsonWriter writer, List<FieldDifference> value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (var field in value)
{
writer.WritePropertyName(field.FieldName);
if (field.type == typeof(string))
writer.WriteValue(field.FieldValue);
else
writer.WriteRawValue(field.FieldValue);
}
writer.WriteEndObject();
}
public override List<FieldDifference> ReadJson(JsonReader reader, Type objectType, List<FieldDifference> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var list = existingValue ?? (List<FieldDifference>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
//Skip comments via MoveToContentAndAssert():
switch (reader.MoveToContentAndAssert().TokenType)
{
case JsonToken.Null:
return null;
case JsonToken.StartObject:
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
{
var name = reader.TokenType == JsonToken.PropertyName ? (string)reader.Value : throw new JsonSerializationException($"Expected PropertyName but got {reader.TokenType}");
FieldDifference field = reader.ReadToContentAndAssert().TokenType switch
{
JsonToken.String => new FieldDifference { FieldName = name, FieldValue = (string)reader.Value, type = typeof(string) },
JsonToken.Integer => new FieldDifference { FieldName = name, FieldValue = Convert.ToString(reader.Value, CultureInfo.InvariantCulture), type = typeof(int)},
JsonToken.Float => new FieldDifference { FieldName = name, FieldValue = Convert.ToString(reader.Value, CultureInfo.InvariantCulture), type = typeof(double)},
JsonToken.Boolean => new FieldDifference { FieldName = name, FieldValue = JsonConvert.ToString((bool)reader.Value), type = typeof(bool)},
JsonToken.Null => new FieldDifference { FieldName = name, FieldValue = null, type = typeof(string)},
JsonToken.Date => new FieldDifference { FieldName = name, FieldValue = JsonConvert.SerializeObject(reader.Value), type = reader.ValueType},
_ => new FieldDifference { FieldName = name, FieldValue = reader.ReadOuterJson(Formatting.None, DateParseHandling.None), type = typeof(object) },
};
list.Add(field);
}
break;
default:
throw new JsonSerializationException($"Unknown token {reader.TokenType}");
}
return list;
}
}
public static partial class JsonExtensions
{
public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) =>
reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, tokenType));
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
ArgumentNullException.ThrowIfNull(reader);
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
ArgumentNullException.ThrowIfNull(reader);
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
public static string ReadOuterJson(this JsonReader reader, Formatting formatting = Formatting.None, DateParseHandling? dateParseHandling = null, FloatParseHandling? floatParseHandling = null)
{
// If you would prefer a null JSON value to return an empty string, remove this line:
if (reader.TokenType == JsonToken.Null)
return null;
var oldDateParseHandling = reader.DateParseHandling;
var oldFloatParseHandling = reader.FloatParseHandling;
try
{
if (dateParseHandling != null)
reader.DateParseHandling = dateParseHandling.Value;
if (floatParseHandling != null)
reader.FloatParseHandling = floatParseHandling.Value;
using (var sw = new StringWriter(CultureInfo.InvariantCulture))
using (var jsonWriter = new JsonTextWriter(sw) { Formatting = formatting })
{
jsonWriter.WriteToken(reader);
return sw.ToString();
}
}
finally
{
reader.DateParseHandling = oldDateParseHandling;
reader.FloatParseHandling = oldFloatParseHandling;
}
}
}
Then apply it to your model as follows:
public class FaactRecordDifferences
{
public string Id { get; set; }
public int Year { get; set; }
[JsonConverter(typeof(FieldDifferenceListConverter))]
public List<FieldDifference> Fields { get; set; }
}
The converter will serialize FieldDifference.Value
as a string when FieldDifference.type
is typeof(string)
, but otherwise it will be written raw as-is as required. Do note that this assumes that FieldDifference.Value
is well-formed JSON when not serialized as a string.
Demo fiddle here.