Advertisement
Guest User

Untitled

a guest
Jan 23rd, 2019
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
F# 15.52 KB | None | 0 0
  1. open System
  2. open FParsec
  3. open System.Text.RegularExpressions
  4. open Newtonsoft.Json
  5. open Microsoft.FSharp
  6. open Microsoft.FSharp.Reflection
  7.  
  8. let inline (^) f x = f x
  9. let (<||>) f1 f2 x = f1 x || f2 x
  10. let (<&&>) f1 f2 x = f1 x && f2 x
  11.  
  12. type Json =
  13.     | JBool
  14.     | JBoolOption
  15.     | JNull
  16.     | JInt
  17.     | JIntOption
  18.     | JFloat
  19.     | JFloatOption
  20.     | JString
  21.     | JDateTimeOffset
  22.     | JDateTimeOffsetOption
  23.     | JStringOption
  24.     | JList of Json list
  25.     | JEmptyObjectOption
  26.     | JObject of (string * Json) list
  27.     | JObjectOption of (string * Json) list
  28.     | JArray of Json
  29.     | JArrayOption of Json
  30.  
  31. [<RequireQualifiedAccess>]
  32. type CollectionType =
  33.     | Array
  34.     | List
  35.     | CSharpList
  36.  
  37. [<RequireQualifiedAccess>]
  38. type GenerationType =
  39.     | JustTypes
  40.     | NewtonsoftJsonProperties
  41.  
  42. let listGenerator = sprintf "%s list"
  43. let arrayGenerator = sprintf "%s array"
  44. let charpListGenerator = sprintf "List<%s>"
  45.  
  46. type TypeIdentification =
  47.     { Name: string
  48.       Attributes: string list}
  49.  
  50. type FieldIdentification =
  51.     { Name: string
  52.       Type: string
  53.       Attributes: string list }
  54.  
  55. type TypeDefinition =
  56.     { TypeIdentification: TypeIdentification
  57.       FieldIdentifications: FieldIdentification list }
  58.  
  59. type NewtonsoftFile =
  60.     { ModuleName: string
  61.       Opens: string seq
  62.       TypeDefinitions: TypeDefinition list }
  63.    
  64. type RecordsFile =
  65.     | NewtonsoftFile of NewtonsoftFile
  66.     | TypeDefinitions of TypeDefinition list
  67.  
  68. let ws = spaces
  69. let str s = pstring s
  70.  
  71. let stringLiteral =
  72.     let escape = anyOf "\"\\/bfnrt"
  73.                   |>> function
  74.                       | 'b' -> "\b"
  75.                       | 'f' -> "\u000C"
  76.                       | 'n' -> "\n"
  77.                       | 'r' -> "\r"
  78.                       | 't' -> "\t"
  79.                       | c   -> string c
  80.  
  81.     let unicodeEscape =
  82.         str "u" >>. pipe4 hex hex hex hex (fun h3 h2 h1 h0 ->
  83.             let hex2int c = (int c &&& 15) + (int c >>> 6)*9
  84.             (hex2int h3)*4096 + (hex2int h2)*256 + (hex2int h1)*16 + hex2int h0
  85.             |> char |> string
  86.         )
  87.  
  88.     between (str "\"") (str "\"")
  89.             (stringsSepBy (manySatisfy (fun c -> c <> '"' && c <> '\\'))
  90.                           (str "\\" >>. (escape <|> unicodeEscape)))
  91.  
  92. let stringOrDateTime (str: string) =
  93.     if DateTimeOffset.TryParse(str, ref (DateTimeOffset())) then JDateTimeOffset
  94.     else JString
  95.  
  96. let jstringOrDate = stringLiteral |>> stringOrDateTime
  97.  
  98. let jnumber = pfloat |>> (fun x -> if x = Math.Floor(x) then JInt else JFloat)
  99.  
  100. let jtrue  = stringReturn "true"  JBool
  101. let jfalse = stringReturn "false" JBool
  102. let jnull  = stringReturn "null" JNull
  103.  
  104. let jvalue, jvalueRef = createParserForwardedToRef()
  105.  
  106. let listBetweenStrings sOpen sClose pElement f =
  107.     between (str sOpen) (str sClose)
  108.             (ws >>. sepBy (pElement .>> ws) (str "," .>> ws) |>> f)
  109.  
  110. let keyValue = tuple2 stringLiteral (ws >>. str ":" >>. ws >>. jvalue)
  111.  
  112. let jlist   = listBetweenStrings "[" "]" jvalue JList
  113. let jobject = listBetweenStrings "{" "}" keyValue JObject
  114.  
  115. do jvalueRef := choice [jobject
  116.                         jlist
  117.                         jstringOrDate
  118.                         jnumber
  119.                         jtrue
  120.                         jnull
  121.                         jfalse]
  122.  
  123. let json = ws >>. jvalue .>> ws .>> eof
  124.  
  125. let parseJsonString str = run json str
  126.  
  127. let isDateTimeOffset = function JDateTimeOffset _ | JDateTimeOffsetOption _ -> true | _ -> false
  128. let isArray = function JArray _ | JArrayOption _ -> true | _ -> false
  129. let isList = function JList _ -> true | _ -> false
  130. let isString = function JStringOption | JString -> true | _ -> false
  131. let isNumber = function JInt | JFloat| JIntOption | JFloatOption -> true | _ -> false
  132. let isNull = function JNull -> true | _ -> false
  133. let isBool = function JBool | JBoolOption -> true | _ -> false
  134. let isObject = function JObject _ | JObjectOption _ -> true | _ -> false
  135.  
  136. let isDateTimeOption = function JDateTimeOffsetOption -> true | _ -> false
  137. let isBoolOption = function JBoolOption -> true | _ -> false
  138. let isStringOption = function JStringOption -> true | _ -> false
  139. let isArrayOption = function JArrayOption _ -> true | _ -> false
  140. let isObjectOption = function JObjectOption _ -> true | _ -> false
  141. let isNumberOption = function JIntOption | JFloatOption -> true | _ -> false
  142. let typeOrder =
  143.     function
  144.     | JInt | JIntOption -> 1
  145.     | JFloat | JFloatOption -> 2
  146.     | _ -> failwith "Not number type"
  147.  
  148. let checkStringOption = List.exists (isNull <||> isStringOption)
  149. let checkArrayOption = List.exists (isNull <||> isArrayOption)
  150. let checkObjectOption = List.exists (isNull <||> isObjectOption)
  151. let checkNumberOption = List.exists (isNull <||> isNumberOption)
  152. let checkBoolOption = List.exists (isNull <||> isBoolOption)
  153. let checkDateTimeOption = List.exists (isNull <||> isDateTimeOption)
  154.  
  155. let (|EmptyList|_|) =
  156.     function
  157.     | [] -> Some EmptyList
  158.     | _ -> None
  159.  
  160. let (|NullList|_|) =
  161.     function
  162.     | list when list |> List.forall isNull -> Some ^ NullList
  163.     | _ -> None
  164.  
  165. let (|NumberList|_|) =
  166.     function
  167.     | list when list |> List.forall (isNumber <||> isNull) -> Some ^ NumberList list
  168.     | _ -> None
  169.  
  170. let (|StringList|_|) =
  171.     function
  172.     | list when list |> List.forall (isString <||> isNull) -> Some ^ StringList list
  173.     | _ -> None
  174.  
  175. let (|DateTimeOffsetList|_|) =
  176.     function
  177.     | list when list |> List.forall (isDateTimeOffset <||> isNull) -> Some ^ DateTimeOffsetList list
  178.     | _ -> None
  179.  
  180. let (|BoolList|_|) =
  181.     function
  182.     | list when list |> List.forall (isBool <||> isNull) -> Some ^ BoolList list
  183.     | _ -> None
  184.  
  185. let (|ObjectList|_|) =
  186.     function
  187.     | list when list |> List.forall (isObject <||> isNull) -> Some ^ ObjectList list
  188.     | _ -> None
  189.  
  190. let (|ListList|_|) =
  191.     function
  192.     | list when list |> List.forall (isList <||> isNull) -> Some ^ ListList list
  193.     | _ -> None
  194.  
  195. let (|ArrayList|_|) =
  196.     function
  197.     | list when list |> List.forall (isArray <||> isNull) -> Some ^ ArrayList list
  198.     | _ -> None
  199.  
  200. let rec aggreagateListToSingleType jsonList =
  201.     let getOptionType isOption istanceType =
  202.         match istanceType, isOption with
  203.         | JInt, true -> JIntOption
  204.         | JFloat, true -> JFloatOption
  205.         | JBool, true -> JBoolOption
  206.         | JString, true -> JStringOption
  207.         | JDateTimeOffset, true -> JDateTimeOffsetOption
  208.         | JObject x, true -> JObjectOption x
  209.         | JArray x, true -> JArrayOption x
  210.         | x, _ -> x
  211.  
  212.     match jsonList with
  213.     | EmptyList -> JEmptyObjectOption
  214.     | NullList -> JEmptyObjectOption
  215.     | StringList list -> JString |> getOptionType (list |> checkStringOption)
  216.     | DateTimeOffsetList list -> JDateTimeOffset |> getOptionType (list |> checkDateTimeOption)
  217.     | BoolList list -> JBool |> getOptionType (list |> checkBoolOption)
  218.     | NumberList list ->
  219.             list
  220.             |> List.filter (not << isNull)
  221.             |> List.distinct
  222.             |> List.map(fun x -> (x, typeOrder x))
  223.             |> List.maxBy snd
  224.             |> fst
  225.             |> getOptionType (list |> checkNumberOption)
  226.     | ObjectList list ->
  227.             list
  228.             |> List.filter (not << isNull)
  229.             |> List.map(function JObject list | JObjectOption list -> list)
  230.             |> List.collect id
  231.             |> List.groupBy fst
  232.             |> List.map(fun (key, value) -> (key, (aggreagateListToSingleType (value |> List.map snd))))
  233.             |> JObject
  234.             |> getOptionType (list |> checkObjectOption)
  235.     | ListList list ->
  236.             list
  237.             |> List.filter (not << isNull)
  238.             |> List.map(function JList x -> x)
  239.             |> List.collect id          
  240.             |> aggreagateListToSingleType
  241.             |> JArray
  242.             |> getOptionType (list |> checkArrayOption)    
  243.     | ArrayList list ->
  244.             list
  245.             |> List.filter (not << isNull)
  246.             |> List.map(function JArray list | JArrayOption list -> list)
  247.             |> aggreagateListToSingleType
  248.             |> JArray
  249.             |> getOptionType (list |> checkArrayOption)
  250.     | _ -> JEmptyObjectOption
  251.  
  252. let rec castArray =
  253.      List.map(fun (key, value) ->
  254.                 match value with
  255.                 | JObject list -> key, JObject ^ castArray list
  256.                 | JList list -> key, JArray ^ aggreagateListToSingleType list
  257.                 | _ -> key, value)
  258.  
  259. let fixName (name: string) =
  260.     let getFirstChar (name: string) = name.Chars 0
  261.     let toUpperFirst name = ((Char.ToUpper ^ getFirstChar name) |> Char.ToString) + name.Substring 1
  262.     let newFieldName = Regex.Replace(name, "[!@#$%^&*()\-=+|/><\[\]\.\\*`]+", "") |> toUpperFirst
  263.  
  264.     if (not <| Char.IsLetter ^ getFirstChar name) && getFirstChar name <> '_' then
  265.         "The" + newFieldName
  266.     else
  267.         newFieldName
  268.  
  269. let rec translateToString listGenerator =
  270.     let getName { Name = name; Type = _; Attributes = _ } = name
  271.  
  272.     fixName
  273.     >> fun name ->
  274.         function
  275.         | JBool ->                  { Name = name; Type = "bool"; Attributes = [] }
  276.         | JBoolOption ->            { Name = name; Type = "bool option"; Attributes = [] }
  277.         | JNull ->                  { Name = name; Type = "Object option"; Attributes = [] }
  278.         | JInt ->                   { Name = name; Type = "int64"; Attributes = [] }
  279.         | JIntOption ->             { Name = name; Type = "int64 option"; Attributes = [] }
  280.         | JFloat ->                 { Name = name; Type = "float"; Attributes = [] }
  281.         | JFloatOption ->           { Name = name; Type = "float option"; Attributes = [] }
  282.         | JString ->                { Name = name; Type = "string"; Attributes = [] }
  283.         | JDateTimeOffset ->        { Name = name; Type = "DateTimeOffset"; Attributes = [] }
  284.         | JDateTimeOffsetOption ->  { Name = name; Type = "DateTimeOffset option"; Attributes = [] }
  285.         | JStringOption ->          { Name = name; Type = "string option"; Attributes = [] }
  286.         | JEmptyObjectOption ->     { Name = name; Type = "Object option"; Attributes = [] }
  287.         | JObject _ ->              { Name = name; Type = name; Attributes = [] }
  288.         | JObjectOption _ ->        { Name = name; Type = sprintf "%s %s" name "option"; Attributes = [] }
  289.         | JArray obj ->             { Name = name; Type = translateToString listGenerator name obj |> getName |> listGenerator; Attributes = [] }
  290.         | JArrayOption obj ->       { Name = name; Type = translateToString listGenerator name obj |> getName |> listGenerator; Attributes = [] }
  291.         | _ -> failwith "translateToString unexcpected"
  292.  
  293. let rec extractObject json =
  294.     match json with
  295.     | JArray obj
  296.     | JArrayOption obj -> extractObject obj
  297.     | JObject _
  298.     | JObjectOption _  -> Some json
  299.     | _ -> None
  300.  
  301. let rec deep outputHandler listGenerator node =
  302.     let rec tailDeep acc jobjs =
  303.         match jobjs with
  304.         | [] -> acc |> List.distinctBy(fun { TypeIdentification = { Name = name } ; FieldIdentifications = _ } -> name)
  305.         | (name, (JObject list))::xs
  306.         | (name, (JObjectOption list))::xs ->
  307.             let newType =
  308.                 list
  309.                 |> List.distinctBy (fun (name, _) -> name)
  310.                 |> List.map (fun (name, value) -> translateToString listGenerator name value)
  311.                 |> fun x -> { TypeIdentification = { Name = name |> fixName; Attributes = [] }; FieldIdentifications = x }
  312.  
  313.             let newJobjs =
  314.                 list
  315.                 |> List.map(fun (key, value) -> (key, extractObject value))
  316.                 |> List.choose(fun (key, v) -> match v with Some j -> Some (key, j) | None -> None)
  317.                        
  318.             tailDeep (newType::acc) (newJobjs @ xs)
  319.         | _ -> failwith "unexpected"
  320.  
  321.     tailDeep [] [node] |> outputHandler
  322.  
  323. let joinByNewLine = sprintf "%s\n%s"
  324.  
  325. let typeIdentificationToString (typeIdentification: TypeIdentification) =
  326.     match typeIdentification.Attributes with
  327.     | [] -> sprintf "type %s =" typeIdentification.Name
  328.     | attribues ->
  329.         attribues
  330.              |> List.fold joinByNewLine ""
  331.              |> (fun x -> sprintf "%s\ntype %s =" x typeIdentification.Name)
  332.  
  333. let fieldIdentificationToString (fieldIdentification: FieldIdentification) =
  334.    match fieldIdentification.Attributes with
  335.    | [] -> sprintf "%s: %s" fieldIdentification.Name fieldIdentification.Type
  336.    | attributes -> sprintf "\n%s\n%s: %s" (attributes |> List.reduce joinByNewLine) fieldIdentification.Name fieldIdentification.Type
  337.    
  338. let fieldsToString =
  339.     function
  340.         | [] -> ""
  341.         | fields ->
  342.                 fields
  343.                 |> List.map fieldIdentificationToString
  344.                 |> List.reduce (sprintf "%s\n\t  %s")
  345.  
  346. let typeDefinitionToString typeDefinition =
  347.     sprintf "%s\n\t{ %s }" (typeIdentificationToString typeDefinition.TypeIdentification) (fieldsToString typeDefinition.FieldIdentifications)
  348.  
  349. let allTypeDefinitionsToString = List.map typeDefinitionToString >> List.reduce (sprintf "%s\n\n%s")
  350.  
  351. let opensToString<'a> = Seq.fold (sprintf "%s\n%s") ""
  352.    
  353. let newtonsoftToString file =
  354.    sprintf "module %s\n%s\n\n%s" file.ModuleName (opensToString file.Opens) (allTypeDefinitionsToString file.TypeDefinitions)
  355.    
  356. let toView =
  357.    function
  358.    | NewtonsoftFile file -> file |> newtonsoftToString
  359.    | TypeDefinitions typeList -> typeList |> allTypeDefinitionsToString
  360.  
  361. type OptionConverter() =
  362.    inherit JsonConverter()
  363.    
  364.    override x.CanConvert(t) =
  365.        t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<option<_>>
  366.  
  367.    override x.WriteJson(writer, value, serializer) =
  368.        let value =
  369.            if value = null then null
  370.            else
  371.                let _,fields = FSharpValue.GetUnionFields(value, value.GetType())
  372.                fields.[0]  
  373.        serializer.Serialize(writer, value)
  374.  
  375.    override x.ReadJson(reader, t, existingValue, serializer) =        
  376.        let innerType = t.GetGenericArguments().[0]
  377.        let innerType =
  378.            if innerType.IsValueType then (typedefof<Nullable<_>>).MakeGenericType([|innerType|])
  379.            else innerType        
  380.        let value = serializer.Deserialize(reader, innerType)
  381.        let cases = FSharpType.GetUnionCases(t)
  382.        if value = null then FSharpValue.MakeUnion(cases.[0], [||])
  383.        else FSharpValue.MakeUnion(cases.[1], [|value|])
  384.  
  385. let newtonsoftGenerator typeList =
  386.    let opens =
  387.        [ "open System"
  388.          "open Newtonsoft.Json"]
  389.    { ModuleName = "Json2FSharp"
  390.      Opens = opens
  391.      TypeDefinitions = typeList }
  392.    |> NewtonsoftFile
  393.  
  394. let justTypesGenerator = TypeDefinitions
  395.  
  396. let generateRecords fileGenerator (str: string) mainObject =
  397.    match parseJsonString str with
  398.    | Success(result, _, _)   ->
  399.        let rootObject =
  400.            match mainObject with
  401.            | Some x -> x
  402.            | None -> "RootObject"
  403.  
  404.        match castArray [rootObject |> fixName, result] with
  405.        | [x] -> printfn "%s" ^ (deep fileGenerator listGenerator x |> toView)
  406.        | _ -> printfn "Failure"
  407.    | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg
  408.  
  409. [<EntryPoint>]
  410. let main argv =
  411.    
  412.  
  413.  
  414.    let testExample = @"
  415.    {
  416.        ""employees"": [[{""name"": ""2012-04-23T18:25:43.511Z""}, {""name"": null} ]],
  417.        ""employees2"": [[{""name"": ""2012-04-23T18:25:43.511Z""}, {""name"": null} ]]    
  418.    }"
  419.  
  420.    generateRecords newtonsoftGenerator testExample None
  421.  
  422.    Console.ReadKey() |> ignore
  423.    0 // return an integer exit code
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement