-1

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?

dbc
  • 104,963
  • 20
  • 228
  • 340
GregH
  • 5,125
  • 8
  • 55
  • 109
  • 1
    You will need to write a converter for this. Do you want to ensure that `FieldValue` is well-formed raw JSON when `type` is not `typeof(string)`, or should it be written raw as-is? – dbc Sep 01 '23 at 16:48
  • @dbc it should be written raw as-is. Ive been unsuccessfully playing with creating a converter for far too long now unforuntately – GregH Sep 01 '23 at 16:50
  • Where is FaactFieldDifference ? What are you talking about? You question is not clear at all. Just post json you have and json you need – Serge Sep 01 '23 at 17:06
  • Do you have some sample JSON for testing? – dbc Sep 01 '23 at 17:10

2 Answers2

0

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; }
}

SyndRain
  • 1
  • 3
0

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.

dbc
  • 104,963
  • 20
  • 228
  • 340