8

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.

venerik
  • 5,766
  • 2
  • 33
  • 43
  • This is not necessarily helpful: YAML seems to be unpopular (outside of Ruby). Can you explain why you want to do this? – ashes999 Mar 17 '16 at 15:00
  • My client has a requirement that my API exposes its specification in both JSON as well as YAML. – venerik Mar 17 '16 at 18:02

7 Answers7

17

You don't actually need to deserialize JSON into strongly typed object you can convert JSON to YAML using dynamic Expando object as well. Here is a small example:-

var json = @"{
        'Name':'Peter',
        'Age':22,
        'CourseDet':{
                'CourseName':'CS',
                'CourseDescription':'Computer Science',
                },
        'Subjects':['Computer Languages','Operating Systems']
        }";

        var expConverter = new ExpandoObjectConverter();
        dynamic deserializedObject = JsonConvert.DeserializeObject<ExpandoObject>(json, expConverter);

        var serializer = new YamlDotNet.Serialization.Serializer();
        string yaml = serializer.Serialize(deserializedObject);

You can see a detailed explanation of both methods i.e. using strongly typed object and dynamic object here.

Yatharth Varshney
  • 1,973
  • 20
  • 22
7

You can convert the JObject to a simpler object that YamlDotNet can serialize:

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 = ConvertJTokenToObject(JsonConvert.DeserializeObject<JToken>(json));

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

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

    static object ConvertJTokenToObject(JToken token)
    {
        if (token is JValue)
            return ((JValue)token).Value;
        if (token is JArray)
            return token.AsEnumerable().Select(ConvertJTokenToObject).ToList();
        if (token is JObject)
            return token.AsEnumerable().Cast<JProperty>().ToDictionary(x => x.Name, x => ConvertJTokenToObject(x.Value));
        throw new InvalidOperationException("Unexpected token: " + token);
    }
}
Ed Ball
  • 1,979
  • 2
  • 14
  • 13
  • This worked for parsing nested objects - See Example 5 http://opensource.adobe.com/Spry/samples/data_region/JSONDataSetSample.html – Markus Apr 29 '19 at 18:04
3

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.

noelicus
  • 14,468
  • 3
  • 92
  • 111
Rob
  • 9,664
  • 3
  • 41
  • 43
  • Thank you very much! It has helped me find the problem: apparently, `YamlDotNet` doesn't like fields. I tried your code: works like a charm. I converted the properties of your classes to fields (as the [real SwaggerDocument](https://github.com/domaindrivendev/Swashbuckle/blob/master/Swashbuckle.Core/Swagger/SwaggerDocument.cs) has) and the output becomes `{}`. Haven't found a solution for this new problem though. – venerik Mar 17 '16 at 14:06
  • Thanks for the update. I could wrap the Swashbuckle classes but that's far from ideal. I'd be happier with a custom `ITypeInspector` as described in the comments of [this answer](http://stackoverflow.com/a/28456466/502395) but I haven't found a way to insert it in the serializer. – venerik Mar 17 '16 at 14:53
  • I checked `YamlDotNet` and I couldn't find place where we can plug custom implementation of `ITypeInspector` :/ – Rob Mar 17 '16 at 14:57
  • Me neither. Thanks for helping me anyway. – venerik Mar 17 '16 at 17:58
1

FWIW I wrote a nuget library to make YamlDotNet work great with Json.Net, honoring all of the JSON.net serialization attributes.

    var yaml = YamlConvert.SerializeObject(obj);
    var obj2 = YamlConvert.DeserializeObject<T>(yaml);

It works by adding a YamlDotNet type serialization class for JTokens (JObject/JArray/JValue)

    var serializer = new SerializerBuilder()
         .WithTypeConverter(new JTokenYamlConverter())
         .Build();
  • i wrote some code like this ``` public static string JsonToYaml(this string json) { var serializer = new SerializerBuilder() .WithTypeConverter(new JTokenYamlConverter()) .Build(); return YamlConvert.YamlConvert.SerializeObject(json, serializer); } ``` return still json, any wrong with me? https://i.imgur.com/fJD3wuB.png – azhe403 Dec 22 '21 at 16:35
0

If you're starting with a JSON string, as the OP has, and you want idiomatic YAML output, it's worth noting that JSON is valid YAML, so YamlDotNet can load it directly, then a simple visitor can adjust the "style" before writing it back out.

using System;
using System.IO;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.RepresentationModel;

public static void Main()
{
    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 yaml = new YamlStream();
    yaml.Load(new StringReader(json));
    yaml.Accept(new JsonToYamlConverterVisitor());
    var outputYaml = new StringWriter();
    yaml.Save(outputYaml, false);
    outputYaml.Flush();
    var yamlString = outputYaml.ToString();
    Console.WriteLine(yamlString);
}

class JsonToYamlConverterVisitor : YamlVisitorBase
{
    public override void Visit(YamlScalarNode scalar)
    {
        if (scalar.Style == ScalarStyle.DoubleQuoted && (scalar.Value == "null" || scalar.Value == "true" || scalar.Value == "false"))
            scalar.Style = ScalarStyle.DoubleQuoted;
        else scalar.Style = ScalarStyle.Plain;
        base.Visit(scalar);
    }

    public override void Visit(YamlSequenceNode sequence)
    {
        sequence.Style = SequenceStyle.Block;
        base.Visit(sequence);
    }

    public override void Visit(YamlMappingNode mapping)
    {
        mapping.Style = MappingStyle.Block;
        base.Visit(mapping);
    }
}

which outputs

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

Run this example on DotNetFiddle: https://dotnetfiddle.net/YUJjzQ

Mike Schenk
  • 1,512
  • 9
  • 21
0
var serializer = new SharpYaml.Serialization.Serializer();
var yaml = serializer.Deserialize(jObject.ToString());
return serializer.Serialize(yaml);

YAML supports JSON deserialization, therefor you can either convert the JObject to a string or just give the serializer your JSON directly without using JObject.

-1

I'm using the following piece of code to build a Yaml element from a JSON and writing it to a file.

Here is the code:

    public static void BuildParametrizedYAML(string element, string element1)
    {
        var jsonBreakers = @"
        {
            'watchers' : {
                'timer' : '10',
                'watcherPool' : '5',
                's3fileExtension' : '.avr.gz',
                'maxRetriesTask' : '3',
                'telemetryFolder' : '/data',
                'telemetryProcessor' : { 
                    'url' : '"+ element1 + @"'
                },
                'breakers' : 
                [
                    {
                        'breakerId' : 'COMMANDER',
                        'firstRetryTimeout' : '1000',
                        'secondRetryTimeout' : '6000',
                        'retries' : '5'
                    },
                    {
                        'breakerId' : 'PROCESSOR',
                        'firstRetryTimeout' : '1000',
                        'secondRetryTimeout' : '6000',
                        'retries' : '30'
                    }
                ],
                'servers' : 
                [
                    {
                        'serverId' : 'vmax',
                        'url' : '"+ element + @"'
                    }
                ]
            }
        }";

        var expConverter = new ExpandoObjectConverter();
        dynamic deserializedObject = JsonConvert.DeserializeObject<ExpandoObject>(jsonBreakers, expConverter);           
        var serializer = new Serializer();
        string JSONContent = serializer.Serialize(deserializedObject);

        var streamLoad = new StringReader(JSONContent);
        var stream = new YamlStream();
        stream.Load(streamLoad);

        using (TextWriter writer = File.CreateText("application.yml"))
        {
            stream.Save(writer, false);
        }
    }

And here is the output:

watchers:
  timer: 10
  watcherPool: 5
  s3fileExtension: .avr.gz
  maxRetriesTask: 3
  telemetryFolder: /data
  telemetryProcessor:
    url: TELEMETRYPROCESSORURL
  breakers:
  - breakerId: COMMANDER
    firstRetryTimeout: 1000
    secondRetryTimeout: 6000
    retries: 5
  - breakerId: PROCESSOR
    firstRetryTimeout: 1000
    secondRetryTimeout: 6000
    retries: 30
  servers:
  - serverId: vmax
    url: TELEMETRYWATCHERVMAXURL
...

Feel free to write me about this.

darangor
  • 19
  • 4