Search code examples
c#jsonyamlyamldotnet

How to convert JSON to YAML using YamlDotNet


I am trying to convert JSON to YAML using YamlDotNet. This is the code I have:

class Program
{
    static void Main(string[] args)
    {
        var json = "{\"swagger\":\"2.0\",\"info\":{\"title\":\"UberAPI\",\"description\":\"MoveyourappforwardwiththeUberAPI\",\"version\":\"1.0.0\"},\"host\":\"api.uber.com\",\"schemes\":[\"https\"],\"basePath\":\"/v1\",\"produces\":[\"application/json\"]}";
        var swaggerDocument = JsonConvert.DeserializeObject(json);

        var serializer = new YamlDotNet.Serialization.Serializer();

        using (var writer = new StringWriter())
        {
            serializer.Serialize(writer, swaggerDocument);
            var yaml = writer.ToString();
            Console.WriteLine(yaml);
        }
    }
}

This is the JSON I provide:

{
   "swagger":"2.0",
   "info":{
      "title":"UberAPI",
      "description":"MoveyourappforwardwiththeUberAPI",
      "version":"1.0.0"
   },
   "host":"api.uber.com",
   "schemes":[
      "https"
   ],
   "basePath":"/v1",
   "produces":[
      "application/json"
   ]
}

This is the YAML I expect:

swagger: '2.0'
info:
  title: UberAPI
  description: MoveyourappforwardwiththeUberAPI
  version: 1.0.0
host: api.uber.com
schemes:
  - https
basePath: /v1
produces:
  - application/json

However, this is the output I get:

swagger: []
info:
  title: []
  description: []
  version: []
host: []
schemes:
- []
basePath: []
produces:
- []

I don't have a clue why all properties are empty arrays.

I also tried typed deserialization and serialization like this:

var specification = JsonConvert.DeserializeObject<SwaggerDocument>(json);
...
serializer.Serialize(writer, swaggerDocument, typeof(SwaggerDocument));

But that produces

{}

Any help is much appreciated.


Solution

  • I think there is problem when json deserialization returns JObject. Looks like yaml serializer doesn't like it.

    I used deserialization with specified type as you mentioned JsonConvert.DeserializeObject<SwaggerDocument>(json) and this is what I get

    Swagger: 2.0
    Info:
      Title: UberAPI
      Description: MoveyourappforwardwiththeUberAPI
      Version: 1.0.0
    Host: api.uber.com
    Schemes:
    - https
    BasePath: /v1
    Produces:
    - application/json
    

    This is my whole code:

    class Program
    {
        static void Main(string[] args)
        {
            var json = "{\"Swagger\":\"2.0\",\"Info\":{\"Title\":\"UberAPI\",\"Description\":\"MoveyourappforwardwiththeUberAPI\",\"Version\":\"1.0.0\"},\"Host\":\"api.uber.com\",\"Schemes\":[\"https\"],\"BasePath\":\"/v1\",\"Produces\":[\"application/json\"]}";
            var swaggerDocument = JsonConvert.DeserializeObject<SwaggerDocument>(json);
            
            var serializer = new YamlDotNet.Serialization.Serializer();
    
            using (var writer = new StringWriter())
            {
                serializer.Serialize(writer, swaggerDocument);
                var yaml = writer.ToString();
                Console.WriteLine(yaml);
            }
        }
    }
    
    public class Info
    {
        public string Title { get; set; }
        public string Description { get; set; }
        public string Version { get; set; }
    }
    
    public class SwaggerDocument
    {
        public string Swagger { get; set; }
        public Info Info { get; set; }
        public string Host { get; set; }
        public List<string> Schemes { get; set; }
        public string BasePath { get; set; }
        public List<string> Produces { get; set; }
    }
    

    update

    Two issues here.

    When deserializing class with fields, by default, json.net won't take them into consideration when doing this job. For this purpose we have to customize the deserialization process by creating a custom contract resolver. We can easily do this by

    var swaggerDocument = JsonConvert.DeserializeObject<SwaggerDocument>(json, new JsonSerializerSettings
    {
        ContractResolver = new MyContractResolver()
    });
    
    public class MyContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
    

    There is second issue when we want to serialize class with fields: Values from fields won't be included into yaml result. I haven't figured out how to deal with this yet.

    Do you have to use Swashbuckle.Swagger type or you can just create wrapper/decorator/DTO for this type?

    I hope it helps you.