skip to Main Content

I have the following simple class structure:

    public class FaactRecordDifferences
    {
        public string Id { get; set; }

        public int Year { get; set; }

        public List<FieldDifference> Fields { get; set; }

    }

    public class FieldDifference
    {
        public string FieldName { get; set; }

        public string FieldValue { get; set; }

        public Type type { get; set; }
    }

And am trying to turn the resulting json into the following output:

    {
        "Id": "U123321",
        "Year": 2024,
       "Fields": {
          "SomeFirstField": 1200.21,
          "AnotherTestField": "SomeStringValue",
          "SomethingHere": 6120.40
        }
      }

where the IM/FM Field arrays are populated as follows:

  • FieldName from FaactFieldDifference becomes json property name

  • FieldValue from FaactFieldDifference becomes json property value

  • Type used to decide if quotes are necessary in json value or not

To clarify, when type is typeof(string), FieldValue should be serialized as a string, but otherwise it should be written raw as-is.

How can I accomplish this?

2

Answers


  1. You can define a custom JsonConverter to do so:

    I did not understand what otherwise should be written raw it is, because the FieldValue is declared as string. Hence, I assume you would need to store them as the Type defined in the type property.

    internal class MyCustomClassConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            List<FieldDifference> objs = (List<FieldDifference>)value;
                    
            writer.WriteStartObject();
            foreach(FieldDifference obj in objs)
            {
                writer.WritePropertyName(obj.FieldName);
                try
                {
                    writer.WriteValue(Convert.ChangeType(obj.FieldValue, obj.type));
                }
                catch
                {
                    writer.WriteValue(obj.FieldValue);
    
                }
            }
            writer.WriteEndObject();
        }
    
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    
        public override bool CanConvert(Type objectType)
        {
            return typeof(List<FieldDifference>).IsAssignableFrom(objectType);
        }
    }
    
    

    And use it at the Fields property:

    public class FaactRecordDifferences
    {
        public string Id { get; set; }
    
        public int Year { get; set; }
    
        [JsonConverter(typeof(MyCustomClassConverter))]
        public List<FieldDifference> Fields { get; set; }
    }
    
    
    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search