I have to serialize a data structure for a 3rd-party interface either to XML or to JSON. The expected result should look like the following (simplified) example:
XML:
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<FooList>
<Foo>
<Name>George</Name>
<Color>Blue</Color>
</Foo>
<Foo>
<Name>Betty</Name>
<Color>Green</Color>
</Foo>
</FooList>
JSON:
[
{
"Foo": {
"Name": "George",
"Color": "Blue"
},
"Foo": {
"Name": "Betty",
"Color": "Green"
}
}
]
So I created the following two classes:
[Serializable, XmlType(AnonymousType = true), XmlRoot(Namespace = "", ElementName = "FooList", IsNullable = false)]
public class FooListResponse
{
[XmlElement("Foo", IsNullable = false)]
public List<Foo> FooList { get; set; }
}
[Serializable]
public class Foo
{
[XmlElement("Name"), JsonProperty("Name")]
public string Name { get; set; }
[XmlElement("Color"), JsonProperty("Color")]
public string Color { get; set; }
}
The result of serialization to XML looks as expected, but the result of serialization to JSON with Newtonsoft JSON.Net is not as expected:
{
"FooList": [
{
"Name": "George",
"Color": "Blue"
},
{
"Name": "Betty",
"Color": "Green"
}
]
}
I use the method JsonConvert.SerializeObject to serialize the data structure to JSON.
Because the expected JSON starts with a square bracket I also test to serialize only the property 'FooList' (and not the complete object of type 'FooListResponse'). The result is different (no surprise), but still not as expected:
[
{
"Name": "George",
"Color": "Blue"
},
{
"Name": "Betty",
"Color": "Green"
}
]
One pair of curly brackets is still missing and the text 'Foo:' in front of each object in the list is missing. Maybe it's a problem of JSON-attributes in my data model or I have to use something different for serializing the data model to JSON.
I hope that the solution for my problem is not "Duplicate all classes in data model for serialization to JSON". The data model of the 3rd-party interface is much more complex as this small example.
Update: I checked the documentation and the vendor of the interface wants to have the duplicate property names (here "Foo"
). Normally a collection of objects is serialized to an array structure (square brackets) and all objects in the collection are serialized without a name in front. I don't know, why they decided to use the duplicate names, but when it is possible to deserialize the JSON to a data structure in their software I have no choice.
As mentioned by Charlieface in comments, your required JSON has duplicated property names within a single object, specifically the name "Foo"
:
[
{
"Foo": { /* Contents of Foo */ },
"Foo": { /* Contents of Foo */ }
}
]
While JSON containing duplicated property names is not malformed, JSON RFC 8259 recommends against it:
The names within an object SHOULD be unique.
You should double-check the documentation of your 3rd-party interface to make sure it really requires duplicated property names.
That being said, if you are certain you need to generate duplicated property names, you will need to write a custom JsonConverter<FooListResponse>
because Json.NET will never create a serialization contract with duplicated names:
public class FooListResponseConverter : JsonConverter<FooListResponse>
{
public override void WriteJson(JsonWriter writer, FooListResponse value, JsonSerializer serializer)
{
var list = value == null ? null : value.FooList;
if (list == null)
{
// TODO: decide what to do for a null list. Maybe write an empty array?
writer.WriteNull();
return;
}
writer.WriteStartArray();
writer.WriteStartObject();
foreach (var foo in list)
{
writer.WritePropertyName("Foo"); // in more current C# versions, use nameof(FooListDeserializationDto.Foo)
serializer.Serialize(writer, foo);
}
writer.WriteEndObject();
writer.WriteEndArray();
}
class FooListDeserializationDto
{
[JsonIgnore] public readonly List<Foo> FooList = new List<Foo>();
// Here we take advantage of the fact that, if Json.NET encounters duplicated property names during deserialization, it will call the setter multiple times.
public Foo Foo { set { FooList.Add(value); } }
}
public override FooListResponse ReadJson(JsonReader reader, Type objectType, FooListResponse existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var dtoList = serializer.Deserialize<List<FooListDeserializationDto>>(reader);
if (dtoList == null)
{
// TODO: decide what to do for a null list. Maybe return an empty FooListResponse?
return null;
}
var list = dtoList.Count == 1
? dtoList[0].FooList
: dtoList.SelectMany(d => d.FooList).ToList();
var fooListResponse = existingValue ?? new FooListResponse();
fooListResponse.FooList = list;
return fooListResponse;
}
}
Then apply it to FooListResponse
as follows:
[Newtonsoft.Json.JsonConverter(typeof(FooListResponseConverter))] // I added the full namespace to clarify this is not System.Text.Json.JsonConverter
[Serializable, XmlType(AnonymousType = true), XmlRoot(Namespace = "", ElementName = "FooList", IsNullable = false)]
public class FooListResponse
{
[XmlElement("Foo", IsNullable = false)]
public List<Foo> FooList { get; set; }
}
And the your FooListResponse
can now be round-tripped to JSON with in the format shown in your question.
Notes:
No changes are required to your Foo
class.
You wrote: The data model of the 3rd-party interface is much more complex as this small example.
While you will need to write a custom converter for FooListResponse
, the Foo
type can be serialized automatically by invoking the serializer from within the converter. Thus, as long as all the complexity of the real interface is in the Foo
class, you are not required to write lots of custom code.
Demo fiddle here.